package ac.il.technion.cs236635.project.core;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import ac.il.technion.cs236635.project.core.policy.AcceptancePolicy;
import ac.il.technion.cs236635.project.core.policy.PolicyConfigurator;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

@Singleton
public class JobsQueue {

	private static final JobsQueue _instance = Guice.createInjector( new PolicyConfigurator( )).getInstance( JobsQueue.class);

	private final List< ServerJob> highPriority = new ListWithId< ServerJob>( );
	
	private final List< ServerJob> lowPriority = new ListWithId< ServerJob>( );
	
	private final Logger log = Logger.getLogger( JobsQueue.class);
	
	private final Map<Integer, Long> timeline = new HashMap< Integer, Long>( );
	
	@Inject
	@Named( "policy")
	private AcceptancePolicy policy;
	
	public static class QueueStatistics {
		
		private long summaryWaitTime = 0;
		
		private long summaryHighTime = 0;
		
		private long summaryLowTime = 0;
		
		private long highCount = 0;
		
		private long lowCount = 0;
		
		private long preemtedCount = 0;

		public synchronized long getSummaryWaitTime() {
			return summaryWaitTime;
		}

		public synchronized void setSummaryWaitTime(long summaryWaitTime) {
			this.summaryWaitTime = summaryWaitTime;
		}

		public synchronized long getSummaryHighTime() {
			return summaryHighTime;
		}

		public synchronized void setSummaryHighTime(long summaryHighTime) {
			this.summaryHighTime = summaryHighTime;
		}

		public synchronized long getSummaryLowTime() {
			return summaryLowTime;
		}

		public synchronized void setSummaryLowTime(long summaryLowTime) {
			this.summaryLowTime = summaryLowTime;
		}

		public synchronized long getHighCount() {
			return highCount;
		}

		public synchronized void setHighCount(long highCount) {
			this.highCount = highCount;
		}

		public synchronized long getLowCount() {
			return lowCount;
		}

		public synchronized void setLowCount(long lowCount) {
			this.lowCount = lowCount;
		}

		public synchronized long getPreemtedCount() {
			return preemtedCount;
		}

		public synchronized void incPreemtedCount() {
			this.preemtedCount ++;
		}
	}
	
	private QueueStatistics statistics = new QueueStatistics( );
	
	public QueueStatistics getStatistics() {
		return statistics;
	}

	public static JobsQueue getInstance( ) {
		synchronized( _instance) {
			return _instance;
		}
	}
	
	protected JobsQueue( ) {
		log.info( "Jobs queue has been initialized");
		log.info( "POLICY: High queue " + highPriority.hashCode( ));
		log.info( "POLICY: Low queue " + lowPriority.hashCode( ));
	}
	
	public synchronized void pushHighPriorityJob( final ServerJob job) {
			if ( policy.accept(job, highPriority)) {
				log.info( "PUSH: New job has been submitted to high queue. Client name: " + job.getClientName( ) + ", job id: " + job.getJobId( ));
				statistics.setHighCount( statistics.getHighCount() + 1);
				timeline.put( job.getJobId( ), System.currentTimeMillis( ));
			} else {
				log.info( "REJECTED: The job with id: " + job.getJobId( ) + " has been rejected. (HIGH) Queue size: " + highPriority.size( ));
			}
	}
	
	public synchronized void pushLowPriorityJob( final ServerJob job) {
			if ( policy.accept( job, lowPriority)) {
				log.info( "PUSH: New job has been submitted to low queue. Client name: " + job.getClientName( ) + ", job id: " + job.getJobId( ));
				statistics.setLowCount( statistics.getLowCount() + 1);
				timeline.put( job.getJobId( ), System.currentTimeMillis( ));
			} else {
				log.info( "REJECTED: The job with id: " + job.getJobId( ) + " has been rejected. (LOW) Queue size: " + lowPriority.size( ));
			}
	}
	
	public synchronized ServerJob popHighPriorityJob( ) {
			if ( highPriority.size( ) == 0)
				return null;
			log.debug( "POP: About to pop scheduled job from high. Queue size before is: " + highPriority.size( ));
			ServerJob job = highPriority.remove(0);
			log.info( "POP: High priority job has poped. Queue size is " + highPriority.size( ) + ". Client name: " + job.getClientName() + ", job id: " + job.getJobId( ));
			if ( timeline.keySet().contains( job.getJobId( ))) {
				long delta = System.currentTimeMillis() - timeline.remove( job.getJobId( ));
				statistics.setSummaryWaitTime( statistics.getSummaryWaitTime( ) + delta);
				statistics.setSummaryHighTime( statistics.getSummaryHighTime( ) + delta);
			} else {
				log.error( "POP(high): Job " + job.getJobId( ) + " is not found (timeline).");
			}
			return job;
	}
	
	public synchronized ServerJob popLowPriorityJob( ) {
			if ( lowPriority.size( ) == 0)
				return null;
			log.debug( "POP: About to pop scheduled job from low. Queue size before is: " + lowPriority.size( ));
			ServerJob job = lowPriority.remove(0);
			log.info( "POP: Low priority job has poped. Queue size is " + lowPriority.size( ) + ". Client name: " + job.getClientName() + ", job id: " + job.getJobId( ));
			if ( timeline.keySet().contains( job.getJobId( ))) {
				long delta = System.currentTimeMillis() - timeline.remove( job.getJobId( ));
				statistics.setSummaryWaitTime( statistics.getSummaryWaitTime( ) + delta);
				statistics.setSummaryLowTime( statistics.getSummaryLowTime( ) + delta);
			} else {
				log.error( "POP(low): Job " + job.getJobId( ) + " is not found (timeline).");
			}
			return job;
	}
	
	public synchronized void pushFirstHighJob( final ServerJob job) {
			highPriority.add( 0, job);
			log.info( "Put the job, with id: " + job.getJobId( ) + " at first place. High priority queue.");
			timeline.put( job.getJobId( ), System.currentTimeMillis( ));
	}
	
	public synchronized void pushFirstLowJob( final ServerJob job) {
			lowPriority.add( 0, job);
			log.info( "Put the job, with id: " + job.getJobId( ) + " at first place. Low priority queue");
			timeline.put( job.getJobId( ), System.currentTimeMillis( ));
	}
	
	public synchronized int getHighJobsNum( ) {
		return highPriority.size( );
	}
	
	public synchronized int getLowJobsNum( ) {
		return lowPriority.size( );
	}

	
	public synchronized boolean removeLowJobById( int id) {
			log.debug( "DELETED: Low queue before delete, job id: " + id + " size: " + lowPriority.size( ));
			Iterator<ServerJob> iterator = lowPriority.iterator( );
			while ( iterator.hasNext( )) {
				ServerJob job = iterator.next( );
				if ( job.getJobId() == id) {
					iterator.remove( );
					timeline.remove( Integer.valueOf(id));
					log.debug( "DELETED: Low queue size became: " + lowPriority.size( ));
					return true;
				}
			}
		log.debug( "DELETED: Low queue job id: " + id + " is not found");
		return false;
	}
	
	public synchronized boolean removeHighJobById( int id) {
			log.debug( "DELETED: High queue before delete, job id: " + id + " size: " + highPriority.size( ));
			Iterator<ServerJob> iterator = highPriority.iterator( );
			while( iterator.hasNext( )) {
				ServerJob job = iterator.next( );
				if ( job.getJobId() == id) {
					iterator.remove( );
					timeline.remove( Integer.valueOf(id));
					log.debug( "DELETED: High queue size became: " + highPriority.size( ));
					return true;
				}
		}
		log.debug( "DELETED: High queue job id: " + id + " is not found");
		return false;
	}
}